package resolver

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"io"
	"io/fs"
	"os"
	"path"
	"path/filepath"
	"strings"
)

type FileType int
type AlgorithmType int

const (
	Md5 AlgorithmType = iota
	Sha1
	Sha256
	Sha512
	Split = " <<<<<<<>>>>>> "
)

const (
	DIR FileType = iota
	FILE
)

type FileResolve struct {
	nextFile fs.FileInfo
	isNoDir  bool
	isZip    bool
	isJar    bool
	*ComparisonResolver
}

func NewFileResolve(rootPath string, isNoDir, isZip, isJar bool) *FileResolve {
	rootPath, _ = filepath.Abs(rootPath)
	return &FileResolve{ComparisonResolver: &ComparisonResolver{RootPath: rootPath, FileMap: make(map[string]*FileInfo)}, isNoDir: isNoDir, isZip: isZip, isJar: isJar}
}

func (fr *FileResolve) Resolve() (*ComparisonResolver, error) {
	fi, e := os.Stat(fr.RootPath)
	if e != nil {
		return nil, e
	}
	fr.nextFile = fi
	if fi.IsDir() {
		fr.FileType = DIR
		fr.resolveDir(fr.RootPath)
	} else {
		fr.FileType = FILE
		fr.resolveFile(fr.RootPath)
	}

	return fr.ComparisonResolver, nil
}

func (fr *FileResolve) resolveDir(curDir string) {
	f, _ := os.Open(curDir)
	list, _ := f.Readdir(-1)
	for _, fi := range list {
		fr.nextFile = fi
		curDir := path.Join(curDir, fr.nextFile.Name())
		if fi.IsDir() {
			path, _ := filepath.Rel(fr.RootPath, curDir)
			nfi := NewFileInfo(path, fr.nextFile.Name(), fr.nextFile.IsDir(), fr.nextFile.Size(), Sha512)
			if !fr.isNoDir {
				fr.FileMap[nfi.Path] = nfi
			}
			fr.Files = append(fr.Files, nfi)
			fr.resolveDir(curDir)
		} else {
			fr.resolveFile(curDir)
		}
	}
}

func (fr *FileResolve) resolveFile(curDir string) {
	ppath, _ := filepath.Rel(fr.RootPath, curDir)
	if ppath == "." {
		ppath = filepath.Base(curDir)
	}
	nfi := NewFileInfo(ppath, fr.nextFile.Name(), fr.nextFile.IsDir(), fr.nextFile.Size(), Sha512)
	nfi.write(curDir)
	fr.FileMap[nfi.Path] = nfi
	fr.Files = append(fr.Files, nfi)

	if strings.HasSuffix(fr.nextFile.Name(), "jar") || strings.HasSuffix(fr.nextFile.Name(), "zip") {
		zipFile, _ := zip.OpenReader(curDir)
		defer zipFile.Close()
		var isComparison bool
		if fr.isZip || (fr.isJar && strings.HasSuffix(fr.nextFile.Name(), "jar")) {
			isComparison = true
		}
		for _, f := range zipFile.File {
			zfi := f.FileInfo()
			fr.nextFile = zfi
			curDir := path.Join(curDir+"!", f.Name)
			path, _ := filepath.Rel(fr.RootPath, curDir)
			nfi := NewFileInfo(strings.TrimPrefix(path, "..\\"), f.Name, fr.nextFile.IsDir(), fr.nextFile.Size(), Sha512)
			if !zfi.IsDir() {
				r, _ := f.OpenRaw()
				nfi.write(r)
			}
			if isComparison && (!zfi.IsDir() || !fr.isNoDir) {
				fr.FileMap[nfi.Path] = nfi
			}
			fr.Files = append(fr.Files, nfi)
		}
	} else if strings.HasSuffix(fr.nextFile.Name(), "tar.gz") || strings.HasSuffix(fr.nextFile.Name(), "tar") {
		var fread io.ReadCloser
		if strings.HasSuffix(fr.nextFile.Name(), "tar.gz") {
			gzf, _ := os.Open(curDir)
			defer gzf.Close()
			fread, _ = gzip.NewReader(gzf)
			defer fread.Close()
		} else {
			fread, _ = os.Open(curDir)
			defer fread.Close()
		}
		tr := tar.NewReader(fread)
		var isComparison bool
		if fr.isZip {
			isComparison = true
		}
		for {
			h, err := tr.Next()
			if err != nil {
				break
			}
			zfi := h.FileInfo()
			fr.nextFile = zfi
			curDir := path.Join(curDir+"!", h.Name)
			path, _ := filepath.Rel(fr.RootPath, curDir)
			nfi := NewFileInfo(path, h.Name, fr.nextFile.IsDir(), fr.nextFile.Size(), Sha512)
			if !zfi.IsDir() {
				nfi.write(tr)
			}
			if isComparison && (!zfi.IsDir() || !fr.isNoDir) {
				fr.FileMap[nfi.Path] = nfi
			}
			fr.Files = append(fr.Files, nfi)
		}
	}
}
